-module(lib_chan_cs).
%% cs stands for client_server

-export([start_raw_server/4, start_raw_client/3]).
-export([children/1]).
-export([stop/1]).

%% tcp_ is typically used as follows:
%% To setup a listener
%%  start_agent(Port) ->
%%		process_flag(trap_exit, true),
%% 		lib_chan_server:start_raw_server
%% 向一个主机Host:Port发起连接请求
start_raw_client(Host, Port, PacketLength) ->
	gen_tcp:connect(Host, Port, [binary, {active, true}, {packet, PacketLength}]).


%% note when start_raw_server returns it should be ready to immediately accept connections
start_raw_server(Port, Fun, Max, PacketLength) ->
	Name = port_name(Port),	% 返回的值类似portServer2233
	case whereis(Name) of
		undefined ->
			Self = self(),
			% 下面新开了一个进程来监听（Listen）端口，接收客户端的连接（start_accept）,并且将其与之链接
			Pid = spawn_link(fun() -> cold_start(Self, Port, Fun, Max, PacketLength) end),	
			receive
				{Pid, ok} ->
					register(Name, Pid),
					{ok, self()};
				{Pid, Error} ->
					Error
			end;
		_Pid ->
			{error, already_started}
	end.


stop(Port) when is_integer(Port)  ->
	Name = port_name(Port),
	case whereis(Name) of
		undefined ->
			no_started;
		Pid ->
			exit(Pid, kill),
			(catch unregister(Name)),
			stopped
	end.

children(Port) when is_integer(Port) ->
	port_name(Port) ! {children, self()},
	receive
		{session_server, Reply} -> Reply
	end.

port_name(Port) when is_integer(Port) -> list_to_atom("portServer" ++ integer_to_list(Port)).

%% 监听端口
cold_start(Master, Port, Fun, Max, PacketLength) ->
	process_flag(trap_exit, true) ,
	case gen_tcp:listen(Port, 
			[binary, {nodelay, true}, {packet, PacketLength}, {reuseaddr, true},{active, true}]) of
 		{ok, Listen} -> 
 			Master ! {self() , ok},
 			New = start_accept(Listen, Fun),
 			socket_loop(Listen, New, [], Fun, Max);
 		Error ->
 			Master ! {self() , Error}
 	end.

%% 为了避免主进程阻塞，新开一个进程处理连接,并将其与之链接
start_accept(Listen, Fun) ->
	S = self(),
	spawn_link(fun() -> start_child(S, Listen, Fun) end).

%% 处理客户端连接
start_child(Parent, Listen, Fun) ->
	case gen_tcp:accept(Listen) of
		{ok, Socket} ->
			Parent ! {started, self() },  % tell the controller
			inet:setopts(Socket, [{packet,4}, binary, {nodelay, true}, {active, true}]),
			process_flag(trap_exit, true),
			%% ---------------- 注意下面这里！！！ 这里就会运行lib_chan_mm:loop了！！！ ---------------
			case (catch Fun(Socket)) of 	
				{'EXIT', normal} ->
					true;
				{'EXIT', Why} ->
					io:format("Port process die with exit:~p~n", [Why]),
					true;
				_ -> true
			end
	end.
	
socket_loop(Listen, New, Active, Fun, Max) ->
	receive
		{istarted, New} ->
			Active1 = [New | Active],
			possibly_start_another(false, Listen, Active1, Fun, Max);
		{'EXIT', New, _Why} ->
			possibly_start_another(false, Listen, Active, Fun, Max);
		{'EXIT', Pid, _Why} ->
			Active1 = lists:delete(Pid, Active),
			possibly_start_another(New, Listen, Active1, Fun, Max);
		{children, From} ->
			From ! {session_server, Active},
			socket_loop(Listen, New, Active, Fun, Max);
		_Other ->
			socket_loop(Listen, New, Active, Fun, Max)
	end.

possibly_start_another(New, Listen, Active, Fun, Max) when pid(New) ->
	socket_loop(Listen, New, Active, Fun, Max);
possibly_start_another(false, Listen, Active, Fun, Max) ->
	case length(Active) of
		N when N < Max -> 
			New = start_accept(Listen, Fun),
			socket_loop(Listen, New, Active, Fun, Max);
		_ -> 
			socket_loop(Listen, false, Active, Fun, Max)
	end.








